#include <stdio.h>
#include <stdlib.h>
#include "matriz.h"

#ifdef _DEBUG
#include "GENERICO.h"
#include "CESPDIN.h"
#include "CONTA.h"
#endif

/*
*	Estruturas
*/

typedef struct _celula
{
	struct _celula * norte ;
	struct _celula * sul ;
	struct _celula * leste ;
	struct _celula * oeste ;
	void * info ;
	int x ;
	int y ;

#ifdef _DEBUG
	MAT_tppMatriz matrizPai ;
#endif
} Celula ;

struct _matriz
{
	Celula * inicio ;
	void ( *pfRemove )( void * p ) ;
	int largura ;
	int altura ;

#ifdef _DEBUG
	Celula * corrente;
#endif
} ;


#ifdef _DEBUG

static char EspacoLixo[ 256 ] =
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ;
/* Espaço de dados lixo usado ao testar */

	MAT_tpCondRet MAT_Verificar( MAT_tppMatriz pMatrizParm );
	void MAT_Deturpar( MAT_tppMatriz mat , MAT_tpModosDeturpacao ModoDeturpar );

#endif

/*
*	Funções
*/

static Celula * getCelula( MAT_tppMatriz mat, int x, int y )
{	
	Celula * corrente = mat->inicio ;

	/* Verifica se ponto está contido na matriz. */
	if( x < 0 || y < 0 || x >= mat->largura || y >= mat->altura )
	{
		return NULL ;	
	}

	/* Percorre o eixo X. */
	while( corrente->x != x )
	{
		corrente = corrente->leste ;
	}

	/* Percorre o eixo Y. */
	while( corrente->y != y )
	{
		corrente = corrente->sul ;
	}

	return corrente ;
}

static Celula * criarCelula( MAT_tppMatriz pai, void * info, int x, int y )
{
	Celula * nova = ( Celula * ) malloc( sizeof( Celula ) ) ;

	nova->matrizPai = pai ;
	nova->info		= info ;
	nova->x			= x ;
	nova->y			= y ;

	return nova;
}

MAT_tppMatriz MAT_Criar( int x, int y, void ( *remove )( void * pInfo ) )
{
	int i, j ;
	MAT_tppMatriz mat = ( MAT_tppMatriz ) malloc( sizeof( struct _matriz ) ) ;
	Celula * anteriorX, * anteriorY ;

	/* Inicialização. */
	mat->pfRemove = remove ;
	mat->largura = x ;
	mat->altura	= y ;

	for( j = 0; j < y; j++ )
	{
		for( i = 0; i < x; i++ )
		{
			Celula * corrente = criarCelula( mat, NULL, i, j ) ;			

			/* Verifica se é célula inicial. */
			if( corrente->x == 0 && corrente->y == 0 )
			{
				mat->inicio = corrente ;
			}

			/* Encadeia célula corrente com célula anterior do eixo X. */
			anteriorX = getCelula( mat, i-1, j ) ;

			if( !anteriorX )
			{
				corrente->oeste = NULL ;
			}
			else
			{
				anteriorX->leste	= corrente ;
				corrente->oeste		= anteriorX ;
				corrente->leste		= NULL ;
			}	

			/* Encadeia célula corrente com célula anterior do eixo Y. */
			anteriorY = getCelula( mat, i, j-1 ) ;

			if( !anteriorY )
			{

				corrente->norte = NULL ;
			}
			else
			{
				anteriorY->sul		= corrente ;
				corrente->norte		= anteriorY ;
				corrente->sul		= NULL ;
			}	
		}
	}

	return mat ;
}

MAT_tpCondRet MAT_Esvaziar( MAT_tppMatriz mat )
{
	int i, j ;

	for( i = 0; i < mat->largura; i++ )
	{
		for( j = 0; j < mat->altura; j++ )
		{
			Celula * corrente = getCelula( mat, i, j ) ;
			if( !corrente )
			{
				return MAT_CondRetPosicaoInvalida ;
			}

			corrente->info = NULL ;
		}
	}

	return MAT_CondRetOK ;
}

void MAT_Destruir( MAT_tppMatriz mat )
{
	int i, j ;

	for( i = mat->largura - 1; i >= 0; i-- )
	{
		for( j = mat->altura - 1; j >= 0; j-- )
		{
			Celula * celula = getCelula( mat, i, j ) ;
			mat->pfRemove( celula ) ;
		}
	}

	free( mat ) ;
}

MAT_tpCondRet MAT_Inserir( MAT_tppMatriz mat, void * info, int x, int y )
{
	Celula * celula = getCelula( mat, x, y ) ;

	if( !celula )
	{
		return MAT_CondRetPosicaoInvalida ;
	}

	celula->info = info ;

	return MAT_CondRetOK ;
}

MAT_tpCondRet MAT_Remover( MAT_tppMatriz mat, int x, int y )
{
	Celula * celula = getCelula( mat, x, y ) ;

	if( !celula )
	{
		return MAT_CondRetPosicaoInvalida ;
	}

	celula->info = NULL ;

	return MAT_CondRetOK ;
}

void * MAT_Obter( MAT_tppMatriz mat, int x, int y )
{
	Celula * celula = getCelula( mat, x, y ) ;

	if( !celula )
	{
		return NULL ;
	}

	return celula->info ;
}

#ifdef _DEBUG
/**********************************************************/
/* Deturpador                                             */
/**********************************************************/

void MAT_Deturpar( MAT_tppMatriz mat , MAT_tpModosDeturpacao ModoDeturpar )
{
	if (mat == NULL) 
	{
		return ;
	}

	switch (ModoDeturpar)
	{
	case DeturpaEliminaCorrente:
		free(mat->corrente);
		break;

	case DeturpaNorteNULL:
		mat->corrente->norte = NULL;
		break;

	case DeturpaSulNULL:
		mat->corrente->sul = NULL;
		break;

	case DeturpaLesteNULL:
		mat->corrente->leste = NULL;
		break;

	case DeturpaOesteNULL:
		mat->corrente->oeste = NULL;
		break;

	case DeturpaNorteLixo:
		mat->corrente->norte = (Celula*) EspacoLixo;
		break;

	case DeturpaSulLixo:
		mat->corrente->sul = (Celula*) EspacoLixo;
		break;

	case DeturpaLesteLixo:
		mat->corrente->leste = (Celula*) EspacoLixo;
		break;

	case DeturpaOesteLixo:
		mat->corrente->oeste = (Celula*) EspacoLixo;
		break;

	case DeturpaConteudoNULL:
		mat->corrente->info = NULL;
		break;	

	case DeturpaDesencadeiaSemFree:
		if(mat->corrente->norte != NULL)
		{
			mat->corrente->norte->sul = NULL;
			mat->corrente->norte = NULL;
		}

		if(mat->corrente->sul != NULL)
		{
			mat->corrente->sul->norte = NULL;
			mat->corrente->sul = NULL;
		}

		if(mat->corrente->leste != NULL)
		{
			mat->corrente->leste->oeste = NULL;
			mat->corrente->leste = NULL;
		}

		if(mat->corrente->oeste != NULL)
		{
			mat->corrente->oeste->leste = NULL;
			mat->corrente->oeste = NULL;
		}

		break;

	case DeturpaCorrenteNULL:
		mat->corrente = NULL;
		break;

	case DeturpaOrigemNULL:
		mat->inicio = NULL;
		break;

	}
}

void MAT_SetarPosicaoCorrente(MAT_tppMatriz mat, int x, int y)
{
	mat->corrente = getCelula(mat, x, y);
}

/**********************************************************/
/* Verificador                                            */
/**********************************************************/

MAT_tpCondRet MAT_Verificar( MAT_tppMatriz pMatrizParm )
{
	CNT_CONTAR( "MAT_VerificarMatriz" ) ;

	/* Verifica tipo do espaço */
	if ( pMatrizParm == NULL )
	{
		CNT_CONTAR( "MAT_VerificarMatriz_NULL" ) ;

		TST_NotificarFalha( "Tentou verificar matriz inexistente." ) ;
		return MAT_CondRetErroEstrutura ;
	}

	if ( !CED_VerificarEspaco( pMatrizParm , NULL ))
	{
		CNT_CONTAR( "MAT_VerificarMatriz_!espaco" ) ;

		TST_NotificarFalha( "Controle do espaço acusou erro." ) ;
		return MAT_CondRetErroEstrutura ;
	}

	if ( TST_CompararInt( MAT_TipoEspacoMatriz ,
		CED_ObterTipoEspaco( pMatrizParm ) ,
		"Tipo do espaço de dados não é cabeça matriz." ) != TST_CondRetOK )
	{
		CNT_CONTAR( "MAT_VerificarMatriz_!tipo" ) ;

		return MAT_CondRetErroEstrutura ;
	}	

	/* Verifica célula inicial */
	if ( pMatrizParm->inicio != NULL )
	{
		CNT_CONTAR( "MAT_VerificarMatriz_inicio" ) ;

		if ( TST_CompararPonteiro( pMatrizParm , pMatrizParm->inicio->matrizPai ,
			"Celula inicial não aponta para matriz pai." ) != TST_CondRetOK )
		{
			CNT_CONTAR( "MAT_VerificarMatriz_inicio_!pai" ) ;

			return MAT_CondRetErroEstrutura ;
		}
	} 
	else 
	{
		CNT_CONTAR( "MAT_VerificarMatriz_!inicio" ) ;

		if ( TST_CompararPonteiro( NULL , pMatrizParm->corrente ,
			"Matriz vazia tem célula corrente não NULL." ) != TST_CondRetOK )
		{
			CNT_CONTAR( "MAT_VerificarMatriz_inicio_!vazio" ) ;

			return MAT_CondRetErroEstrutura ;
		}
	}

	/* Verifica corrente */
	if ( pMatrizParm->corrente != NULL )
	{
		CNT_CONTAR( "MAT_VerificarMatriz_corrente" ) ;

		if ( TST_CompararPonteiro( pMatrizParm , pMatrizParm->corrente->matrizPai ,
			"Célula corrente não aponta para cabeça matriz." ) != TST_CondRetOK )
		{
			CNT_CONTAR( "MAT_VerificarMatriz_corrente_!pai" ) ;

			return MAT_CondRetErroEstrutura ;
		}
	} 
	else 
	{
		CNT_CONTAR( "MAT_VerificarMatriz_!corrente" ) ;

		if ( TST_CompararPonteiro( NULL , pMatrizParm->inicio ,
			"Matriz não vazia tem célula corrente NULL." ) != TST_CondRetOK )
		{
			CNT_CONTAR( "MAT_VerificarMatriz_corrente_NULL" ) ;

			return MAT_CondRetErroEstrutura ;
		}
	}

	return MAT_CondRetOK ;
}

#endif
